/******************************************************************************\
  Wire link support
\******************************************************************************/

local floor = math.floor
local Clamp = math.Clamp

registerCallback("construct", function(self) 
	self.triggercache = {}
end)

registerCallback("postexecute", function(self)
	for _,ent,portname,value in pairs_map(self.triggercache, unpack) do
		WireLib.TriggerInput(ent, portname, value)
	end
	
	self.triggercache = {}
end)

local function TriggerInput(self,ent, portname, value, typename)
	if not ent.Inputs[portname] then return value end
	if ent.Inputs[portname].Type ~= typename then return value end
	
	self.triggercache[ent:EntIndex().."__"..portname] = { ent, portname, value }
	
	return value
end

local function validWirelink(self, ent)
	if not validEntity(ent) then return false end
	if not ent.extended then return false end
	local player = E2Lib.getOwner(self, ent)
	if player == nil then return false end
	if not player:IsValid() then return false end
	if player != self.player then
	--	E2Lib.abuse(self.player)
		return false
	end
	
	return true
end

/******************************************************************************/

local function WriteStringZero(entity, address, string)
	if not entity:WriteCell(address+#string, 0) then return 0 end
	
	for index = 1,#string do
		local byte = string.byte(string,index)
		if not entity:WriteCell(address+index-1, byte) then return 0 end
	end
	return address+#string+1
end

local function ReadStringZero(entity, address)
	local byte
	local tbl = {}
	for index = address,address+16384 do
		byte = entity:ReadCell(index, byte)
		if not byte then return "" end
		if byte < 1 then break end
		if byte >= 256 then byte = 32 end
		table.insert(tbl,string.char(floor(byte)))
	end
	return table.concat(tbl)
end

local wa_lookup -- lookup table for tables that were already serialized.
local function WriteArray(entity, address, data)
	-- check if enough space is available
	if not entity:WriteCell(address+#data-1, 0) then return 0 end
	
	-- write the trailing 0 byte.
	entity:WriteCell(address+#data, 0)
	local free_address = address+#data+1
	
	for index, value in ipairs(data) do
		local tp = type(value)
		if tp == "number" then
			if not entity:WriteCell(address+index-1, value) then return 0 end
		elseif tp == "string" then
			if not entity:WriteCell(address+index-1, free_address) then return 0 end
			free_address = WriteStringZero(entity, free_address, value)
			if free_address == 0 then return 0 end
		elseif tp == "table" then
			if wa_lookup[value] then
				if not entity:WriteCell(address+index-1, wa_lookup[value]) then return 0 end
			else
				wa_lookup[value] = free_address
				if not entity:WriteCell(address+index-1, free_address) then return 0 end
				free_address = WriteArray(entity, free_address, value)
			end
		elseif tp == "Vector" then
			if not entity:WriteCell(address+index-1, free_address) then return 0 end
			free_address = WriteArray(entity, free_address, { value[1], value[2], value[3] })
		end
	end
	return free_address
end

/******************************************************************************/

registerType("wirelink", "xwl", nil,
	nil,
	nil,
	function(retval)
		if validEntity(retval) then return end
		if retval == nil then return end
		if not retval.EntIndex then error("Return value is neither nil nor an Entity (and thus not a wirelink), but a "..type(retval).."!",0) end
	end,
	function(v)
		return not validEntity(v)
	end
)

/******************************************************************************/

__e2setcost(2) -- temporary

e2function wirelink operator=(wirelink lhs, wirelink rhs)
	self.vars[lhs] = rhs
	self.vclk[lhs] = true
	return rhs
end

/******************************************************************************/

e2function number operator_is(wirelink value)
	if not validWirelink(self, value) then return 0 end
	return 1
end

e2function number operator==(wirelink lhs, wirelink rhs)
	if lhs == rhs then return 1 else return 0 end
end

e2function number operator!=(wirelink lhs, wirelink rhs)
	if lhs ~= rhs then return 1 else return 0 end
end

/******************************************************************************/

e2function number wirelink:isHiSpeed()
	if not validWirelink(self, this) then return 0 end
	if this.WriteCell or this.ReadCell then return 1 else return 0 end
end

e2function entity wirelink:entity()
	return this
end

/******************************************************************************/

e2function number wirelink:hasInput(string portname)
	if not validWirelink(self, this) then return 0 end
	
	if not this.Inputs[portname] then return 0 end
	return 1
end

e2function number wirelink:hasOutput(string portname)
	if not validWirelink(self, this) then return 0 end
	
	if not this.Outputs[portname] then return 0 end
	return 1
end

/******************************************************************************/

registerCallback("postinit", function()
	
	local getf, setf
	-- generate getters and setters for all types
	for typename,id,zero,input_serializer,output_serializer in pairs_map(wire_expression_types, unpack) do
		local fname = typename == "NORMAL" and "NUMBER" or typename
		
		-- for T:number() etc
		local getter = fname:lower()
		
		-- for T:setNumber() etc
		local setter = "set"..fname:sub(1,1):upper()..fname:sub(2):lower()
		
		if input_serializer then
			if type(zero) == "table" and not next(zero) then
				-- table/array
				function getf(self, args)
					local this, portname = args[2], args[3]
					this, portname = this[1](self, this), portname[1](self, portname)
					
					if not validWirelink(self, this) then return {} end
					
					if not this.Outputs[portname] then return {} end
					if this.Outputs[portname].Type ~= typename then return {} end
					
					return input_serializer(self, this.Outputs[portname].Value)
				end
			else
				-- all other types with input serializers
				function getf(self, args)
					local this, portname = args[2], args[3]
					this, portname = this[1](self, this), portname[1](self, portname)
					
					if not validWirelink(self, this) then return zero end
					
					if not this.Outputs[portname] then return zero end
					if this.Outputs[portname].Type ~= typename then return zero end
					
					return input_serializer(self, this.Outputs[portname].Value)
				end
			end
		else
			-- all types without an input serializer
			-- a check for {} is not needed here, since array and table both have input serializers and are thus handled in the above branch.
			function getf(self, args)
				local this, portname = args[2], args[3]
				this, portname = this[1](self, this), portname[1](self, portname)
				
				if not validWirelink(self, this) then return zero end
				
				if not this.Outputs[portname] then return zero end
				if this.Outputs[portname].Type ~= typename then return zero end
				
				return this.Outputs[portname].Value
			end
		end
		
		if output_serializer then
			function setf(self, args)
				local this, portname, value = args[2], args[3], args[4]
				this, portname, value = this[1](self, this), portname[1](self, portname), value[1](self, value)
				
				if not validWirelink(self, this) then return value end
				if not this.Inputs then return value end
				
				TriggerInput(self, this, portname, output_serializer(self, value), typename)
				return value
			end
		else
			function setf(self, args)
				local this, portname, value = args[2], args[3], args[4]
				this, portname, value = this[1](self, this), portname[1](self, portname), value[1](self, value)
				
				if not validWirelink(self, this) then return value end
				if not this.Inputs then return value end
				
				TriggerInput(self, this, portname, value, typename)
				return value
			end
		end
		
		registerFunction(getter, "xwl:s", id, getf, 5)
		registerOperator("idx", id.."=xwls", id, getf, 5)
		registerFunction(setter, "xwl:s"..id, id, setf, 5)
		registerOperator("idx", id.."=xwls"..id, id, setf, 5)
	end
end)

__e2setcost(15) -- temporary

e2function void wirelink:setXyz(vector value)
	if not validWirelink(self, this) then return end
	
	TriggerInput(self, this, "X", value[1], "NORMAL")
	TriggerInput(self, this, "Y", value[2], "NORMAL")
	TriggerInput(self, this, "Z", value[3], "NORMAL")
end

e2function vector wirelink:xyz()
	if not validWirelink(self, this) then return { 0, 0, 0 } end
	
	local x, y, z = this.Outputs["X"], this.Outputs["Y"], this.Outputs["Z"] 
	
	if not x or not y or not z then return { 0, 0, 0 } end
	if x.Type ~= "NORMAL" or y.Type ~= "NORMAL" or z.Type ~= "NORMAL" then return { 0, 0, 0 } end
	return { x.Value, y.Value, z.Value }
end

/******************************************************************************/
-- XWL:inputs/outputs/inputType/outputType by jeremydeath

--- Returns an array of all the inputs that <this> has without their types. Returns an empty array if it has none
e2function array wirelink:inputs()
	if not validWirelink(self, this) then return {} end
	if(!this.Inputs) then return {} end
	
	local InputNames = {}
	for k,v in pairs_sortvalues(this.Inputs, WireLib.PortComparator) do
		table.insert(InputNames,k)
	end
	return InputNames
end

--- Returns an array of all the outputs that <this> has without their types. Returns an empty array if it has none
e2function array wirelink:outputs()
	if not validWirelink(self, this) then return {} end
	if(!this.Outputs) then return {} end
	
	local OutputNames = {}
	for k,v in pairs_sortvalues(this.Outputs, WireLib.PortComparator) do
		table.insert(OutputNames,k)
	end
	return OutputNames 
end

--- Returns the type of input that <Input> is in lowercase. ( "NORMAL"  is changed to "number" )
e2function string wirelink:inputType(string Input)
	if not validWirelink(self, this) then return "" end
	if(!this.Inputs or !this.Inputs[Input]) then return "" end
	
	local Type = this.Inputs[Input].Type or ""
	if Type == "NORMAL" then Type = "number" end
	return string.lower(Type)
end

--- Returns the type of output that <Output> is in lowercase. ( "NORMAL"  is changed to "number" )
e2function string wirelink:outputType(string Output)
	if not validWirelink(self, this) then return "" end
	if(!this.Outputs or !this.Outputs[Output]) then return "" end
	
	local Type = this.Outputs[Output].Type or ""
	if Type == "NORMAL" then Type = "number" end
	return string.lower(Type)
end

/******************************************************************************/

__e2setcost(5) -- temporary

e2function number wirelink:writeCell(address, value)
	if not validWirelink(self, this) then return 0 end
	
	if not this.WriteCell then return 0 end
	if this:WriteCell(address, value) then return 1 else return 0 end
end

e2function number wirelink:readCell(address)
	if not validWirelink(self, this) then return 0 end
	
	if not this.ReadCell then return 0 end
	return this:ReadCell(address) or 0
end

e2function array wirelink:readArray(start, size) 
	if !validWirelink(self, this) or !this.ReadCell then return {} end
	
	self.prf = self.prf + size
	
	local ret = {}
	
	for i = 1, size do
		ret[i] = this:ReadCell(start + (i - 1))
	end
	
	return ret
end

e2function number wirelink:operator[](address, value)
	if not validWirelink(self, this) then return value end
	
	if not this.WriteCell then return value end
	this:WriteCell(address, value)
	return value
end
e2function number wirelink:operator[](address) = e2function number wirelink:readCell(address)

/******************************************************************************/

__e2setcost(20) -- temporary

--- XWL[N,vector]=V
e2function vector wirelink:operator[T](address, vector value)
	if not validWirelink(self, this) then return value end
	
	if not this.WriteCell then return value end
	this:WriteCell(address, value[1])
	this:WriteCell(address+1, value[2])
	this:WriteCell(address+2, value[3])
	return value
end

--- V=XWL[N,vector]
e2function vector wirelink:operator[T](address)
	if not validWirelink(self, this) then return { 0, 0, 0 } end
	
	if not this.ReadCell then return 0 end
	return {
		this:ReadCell(address) or 0,
		this:ReadCell(address+1) or 0,
		this:ReadCell(address+2) or 0,
	}
end

--- XWL[N,string]=S
e2function string wirelink:operator[T](address, string value)
	if not validWirelink(self, this) or not this.WriteCell then return "" end
	WriteStringZero(this, address, value)
	return value
end

--- S=XWL[N,string]
e2function string wirelink:operator[T](address)
	if not validWirelink(self, this) or not this.ReadCell then return "" end
	return ReadStringZero(this, address)
end

/******************************************************************************/

__e2setcost(20) -- temporary

local function conv(vec)
	local r = Clamp(floor(vec[1]/28),0,9)
	local g = Clamp(floor(vec[2]/28),0,9)
	local b = Clamp(floor(vec[3]/28),0,9)
	
	return floor(r)*100+floor(g)*10+floor(b)
end

local function WriteString(self, entity, string, X, Y, textcolor, bgcolor, Flash)
	if type(textcolor) ~= "number" then textcolor = conv(textcolor) end
	if type(bgcolor) ~= "number" then bgcolor = conv(bgcolor) end
	
	if not validWirelink(self, entity) or not entity.WriteCell then return end
	
	textcolor = Clamp(floor(textcolor), 0, 999)
	bgcolor = Clamp(floor(bgcolor), 0, 999)
	Flash = Flash ~= 0 and 1 or 0
	local Params = Flash*1000000 + bgcolor*1000 + textcolor
	
	local Xorig = X
	for i = 1,#string do
		local Byte = string.byte(string,i)
		if Byte == 10 then
			Y = Y+1
			X = Xorig -- shouldn't this be 0 as well? would be more consistent.
		else
			if X >= 30 then
				X = 0
				Y = Y + 1
			end
			local Address = 2*(Y*30+(X))
			X = X + 1
			if Address>=1080 or Address<0 then return end
			entity:WriteCell(Address, Byte)
			entity:WriteCell(Address+1, Params)
		end
	end
end

e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash)
	WriteString(self, this,text,x,y,textcolor,bgcolor,flash)
end


e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor)
	WriteString(self, this,text,x,y,textcolor,bgcolor,0)
end


e2function void wirelink:writeString(string text, x, y, textcolor)
	WriteString(self, this,text,x,y,textcolor,0,0)
end

e2function void wirelink:writeString(string text, x, y)
	WriteString(self, this,text,x,y,999,0,0)
end

e2function void wirelink:writeString(string text, x, y,        textcolor, vector bgcolor, flash) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash)
e2function void wirelink:writeString(string text, x, y, vector textcolor,        bgcolor, flash) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash)
e2function void wirelink:writeString(string text, x, y, vector textcolor, vector bgcolor, flash) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor, flash)

e2function void wirelink:writeString(string text, x, y,        textcolor, vector bgcolor) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor)
e2function void wirelink:writeString(string text, x, y, vector textcolor,        bgcolor) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor)
e2function void wirelink:writeString(string text, x, y, vector textcolor, vector bgcolor) = e2function void wirelink:writeString(string text, x, y, textcolor, bgcolor)

e2function void wirelink:writeString(string text, x, y, vector textcolor) = e2function void wirelink:writeString(string text, x, y, textcolor)

-- Unicode strings
local function WriteUnicodeString(entity, string, X, Y, textcolor, bgcolor, Flash)
	if type(textcolor) ~= "number" then textcolor = conv(textcolor) end
	if type(bgcolor) ~= "number" then bgcolor = conv(bgcolor) end
	
	if not validWirelink(self, entity) or not entity.WriteCell then return end
	
	textcolor = Clamp(floor(textcolor), 0, 999)
	bgcolor = Clamp(floor(bgcolor), 0, 999)
	Flash = Flash ~= 0 and 1 or 0
	local Params = Flash*1000000 + bgcolor*1000 + textcolor
	
	local Xorig = X
	local i = 1
	while i <= #string do
		local Byte = string.byte(string,i)
		if Byte == 10 then
			Y = Y+1
			X = Xorig -- shouldn't this be 0 as well? would be more consistent.
		else
			if Byte >= 128 then
				if Byte >= 240 then
					-- 4 byte sequence (unsupported by engine, but it should only occupy one character on the console screen)
					if i + 3 > #string then
						Byte = 0
					else
						Byte = (Byte & 7) * 262144
						Byte = Byte + (string.byte (string, i + 1) & 63) * 4096
						Byte = Byte + (string.byte (string, i + 2) & 63) * 64
						Byte = Byte + (string.byte (string, i + 3) & 63)
					end
					i = i + 3
				elseif Byte >= 224 then
					-- 3 byte sequence
					if i + 2 > #string then
						Byte = 0
					else
						Byte = (Byte & 15) * 4096
						Byte = Byte + (string.byte (string, i + 1) & 63) * 64
						Byte = Byte + (string.byte (string, i + 2) & 63)
					end
					i = i + 2
				elseif Byte >= 192 then
					-- 2 byte sequence
					if i + 1 > #string then
						Byte = 0
					else
						Byte = (Byte & 31) * 64
						Byte = Byte + (string.byte (string, i + 1) & 63)
					end
					i = i + 1
				else
					-- invalid sequence
					Byte = 0
				end
			end
			if X >= 30 then
				X = 0
				Y = Y + 1
			end
			local Address = 2*(Y*30+(X))
			X = X + 1
			if Address>=1080 or Address<0 then return end
			entity:WriteCell(Address, Byte)
			entity:WriteCell(Address+1, Params)
		end
		i = i + 1
	end
end

e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash)
	WriteUnicodeString(this,text,x,y,textcolor,bgcolor,flash)
end


e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor)
	WriteUnicodeString(this,text,x,y,textcolor,bgcolor,0)
end


e2function void wirelink:writeUnicodeString(string text, x, y, textcolor)
	WriteUnicodeString(this,text,x,y,textcolor,0,0)
end

e2function void wirelink:writeUnicodeString(string text, x, y)
	WriteUnicodeString(this,text,x,y,999,0,0)
end

e2function void wirelink:writeUnicodeString(string text, x, y,        textcolor, vector bgcolor, flash) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash)
e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor,        bgcolor, flash) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash)
e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor, vector bgcolor, flash) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor, flash)

e2function void wirelink:writeUnicodeString(string text, x, y,        textcolor, vector bgcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor)
e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor,        bgcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor)
e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor, vector bgcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor, bgcolor)

e2function void wirelink:writeUnicodeString(string text, x, y, vector textcolor) = e2function void wirelink:writeUnicodeString(string text, x, y, textcolor)

/******************************************************************************/

--- Writes a null-terminated string to the given address. Returns the next free address or 0 on failure.
e2function number wirelink:writeString(address, string data)
	if not validWirelink(self, this) or not this.WriteCell then return 0 end
	return WriteStringZero(this, address, data)
end

--- Reads a null-terminated string from the given address. Returns an empty string on failure.
e2function string wirelink:readString(address)
	if not validWirelink(self, this) or not this.ReadCell then return "" end
	return ReadStringZero(this, address)
end

/******************************************************************************/

--- Writes an array's elements into a piece of memory. Strings and sub-tables (angles, vectors, matrices) are written as pointers to the actual data. Strings are written null-terminated.
e2function number wirelink:writeArray(address, array data)
	if not validWirelink(self, this) or not this.WriteCell then return 0 end
	wa_lookup = {}
	local ret = WriteArray(this,address,data)
	wa_lookup = nil
	return ret
end

e2function number wirelink:writeTable(address, table data )
	if not validWirelink(self, this) or not this.WriteCell then return 0 end
	wa_lookup = {}
	local ret = WriteArray(this,address,data.n)
	wa_lookup = nil
	return ret
end

__e2setcost(nil) -- temporary
